#include <crt_util.h>

void* mm_alloc(size_t size)
{
    void* p;

    p = malloc(size);
    if (p == NULL) { }
    return p;
}

void* mm_calloc(size_t size)
{
    void* p;

    p = mm_alloc(size);

    if (p) {
        mm_memzero(p, size);
    }

    return p;
}
void mm_free(void* p)
{
    free(p);
}
mm_pool_t* mm_create_pool(size_t size)
{
    mm_pool_t* p;

    p = mm_memalign(MM_POOL_ALIGNMENT, size);
    if (p == NULL) {
        return NULL;
    }

    p->d.last = (mm_char*)p + sizeof(mm_pool_t);
    p->d.end = (mm_char*)p + size;
    p->d.next = NULL;
    p->d.failed = 0;

    size = size - sizeof(mm_pool_t);
    p->max = (size < MM_MAX_ALLOC_FROM_POOL) ? size : MM_MAX_ALLOC_FROM_POOL;

    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;

    return p;
}

void mm_destroy_pool(mm_pool_t* pool)
{
    mm_pool_t *p, *n;
    mm_pool_large_t* l;
    mm_pool_cleanup_t* c;

    for (c = pool->cleanup; c; c = c->next) {
        if (c->handler) {
            c->handler(c->data);
        }
    }

    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            mm_free(l->alloc);
        }
    }

    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        mm_free(p);

        if (n == NULL) {
            break;
        }
    }
}
void mm_reset_pool(mm_pool_t* pool)
{
    mm_pool_t* p;
    mm_pool_large_t* l;

    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            mm_free(l->alloc);
        }
    }

    for (p = pool; p; p = p->d.next) {
        p->d.last = (mm_char*)p + sizeof(mm_pool_t);
        p->d.failed = 0;
    }

    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

static void* ngx_palloc_large(mm_pool_t* pool, size_t size)
{
    void* p;
    mm_uint_t n;
    mm_pool_large_t* large;

    p = mm_alloc(size);
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }

        if (n++ > 3) {
            break;
        }
    }

    large = mm_palloc(pool, sizeof(mm_pool_large_t));
    if (large == NULL) {
        mm_free(p);
        return NULL;
    }

    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

static void* ngx_palloc_block(mm_pool_t* pool, size_t size)
{
    mm_char* m;
    size_t psize;
    mm_pool_t *p, *new, *current;

    psize = (size_t)(pool->d.end - (mm_char*)pool);

    m = mm_memalign(MM_POOL_ALIGNMENT, psize);
    if (m == NULL) {
        return NULL;
    }

    new = (mm_pool_t*)m;

    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;

    m += sizeof(mm_pool_data_t);
    m = mm_align_ptr(m, MM_ALIGNMENT);
    new->d.last = m + size;

    current = pool->current;

    for (p = current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            current = p->d.next;
        }
    }

    p->d.next = new;

    pool->current = current ? current : new;

    return m;
}
void* mm_palloc(mm_pool_t* pool, size_t size)
{
    mm_char* m;
    mm_pool_t* p;

    if (size <= pool->max) {
        p = pool->current;

        do {
            m = mm_align_ptr(p->d.last, MM_ALIGNMENT);

            if ((size_t)(p->d.end - m) >= size) {
                p->d.last = m + size;

                return m;
            }

            p = p->d.next;

        } while (p);

        return ngx_palloc_block(pool, size);
    }

    return ngx_palloc_large(pool, size);
}

void* mm_pnalloc(mm_pool_t* pool, size_t size)
{
    mm_char* m;
    mm_pool_t* p;

    if (size <= pool->max) {
        p = pool->current;

        do {
            m = p->d.last;

            if ((size_t)(p->d.end - m) >= size) {
                p->d.last = m + size;

                return m;
            }

            p = p->d.next;

        } while (p);

        return ngx_palloc_block(pool, size);
    }

    return ngx_palloc_large(pool, size);
}
void* mm_pcalloc(mm_pool_t* pool, size_t size)
{
    void* p;

    p = mm_palloc(pool, size);
    if (p) {
        mm_memzero(p, size);
    }

    return p;
}

void* mm_pmemalign(mm_pool_t* pool, size_t size, size_t alignment)
{
    void* p;
    mm_pool_large_t* large;

    p = mm_memalign(alignment, size);
    if (p == NULL) {
        return NULL;
    }

    large = mm_palloc(pool, sizeof(mm_pool_large_t));
    if (large == NULL) {
        mm_free(p);
        return NULL;
    }

    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

mm_int_t mm_pfree(mm_pool_t* pool, void* p)
{
    mm_pool_large_t* l;

    for (l = pool->large; l; l = l->next) {
        if (p == l->alloc) {
            mm_free(l->alloc);
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}
